package com.evil.application.strategy.reporting;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.crypto.digest.MD5;
import com.evil.application.enums.reporting.ReportingTypeEnum;
import com.evil.application.enums.reporting.SummaryTypeEnum;
import com.evil.application.pojo.dto.reporting.ApplicationReportingDTO;
import com.evil.application.pojo.dto.reporting.ReportingTargetDTO;
import com.evil.application.pojo.dto.reporting.data.ReportingDataRespDTO;
import com.evil.application.pojo.dto.reporting.data.ReportingTableDataDTO;
import com.evil.application.pojo.dto.reporting.data.ReportingTableDataTargetDTO;
import com.evil.common.core.enums.RCodeEnum;
import com.evil.common.core.exception.BusinessException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 报表类型-汇总表
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
@Service
@AllArgsConstructor
public class ReportingDataSummaryChartImpl implements ReportingDataChart {

    @Override
    public ReportingTypeEnum type() {
        return ReportingTypeEnum.SUMMARY_CHART;
    }

    /**
     * 统计
     *
     * @param reportingDTO reportingDTO
     * @param aggregations aggregations
     * @return ReportingDataRespDTO
     */
    @Override
    public ReportingDataRespDTO calculation(ApplicationReportingDTO reportingDTO, Aggregations aggregations) {
        ReportingDataRespDTO reportingDataRespDTO = new ReportingDataRespDTO(reportingDTO.getReportingKey());
        if (null == aggregations) {
            return reportingDataRespDTO;
        }

        SummaryInfo summaryInfo = this.summaryInfo(reportingDTO, aggregations);
        List<List<ReportingTableDataDTO>> reportingHeaders = this.reportingHeaders(summaryInfo);
        reportingDataRespDTO.setReportingHeaders(reportingHeaders);

        SummaryTree summaryTree = this.summaryTree(reportingDTO, aggregations);

        // 列数
        int cellNum = reportingHeaders.stream().max(Comparator.comparingInt(List::size)).map(List::size).orElse(0);
        // 行map
        Map<String, SummaryTable> rowMap = new LinkedHashMap<>();
        // 构造 行map 返回最大行号
        int rowNum = this.structureRow(0, summaryTree, rowMap, new ArrayList<>());
        // 表格
        ReportingTableDataDTO[][] collectValues = ReportingTableDataDTO.generateEmptyCell(rowNum <= 0 ? 1 : rowNum, cellNum);
        // 列Map
        Map<String, SummaryTable> cellMap = new LinkedHashMap<>();
        // 指标map
        Map<String, TargetInfo> targetMap = new LinkedHashMap<>();
        // 构造列Map
        this.structureCell(reportingHeaders, 0, reportingDTO.getReportingHeaders().size(), null, new ArrayList<>(), cellMap, targetMap);
        // 列汇总 行
        ReportingTargetDTO[] columnTarget = new ReportingTargetDTO[cellNum];
        // 填充表格
        this.filling(collectValues, columnTarget, summaryTree, rowMap, cellMap, targetMap, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());

        reportingDataRespDTO.setCollectValues(Arrays.stream(collectValues).map(ListUtil::toList).collect(Collectors.toList()));
        if (rowNum > 0) {
            reportingDataRespDTO.getCollectValues().add(this.calColumnSummary(collectValues, columnTarget));
        }
        return reportingDataRespDTO;
    }

    private int structureRow(int rowNum, SummaryTree summaryTree, Map<String, SummaryTable> rowMap, List<String> titles) {
        if (SummaryType.ROOT.equals(summaryTree.getType()) || SummaryType.ROW.equals(summaryTree.getType())) {
            if (SummaryType.ROW.equals(summaryTree.getType())) {
                titles.add(summaryTree.getSummary().getValue());
            }
            // 当前是行维度，子级却不是行维度，证明当前是最后一个行维度
            if (SummaryType.ROW.equals(summaryTree.getType()) && !SummaryType.ROW.equals(summaryTree.getChildType())) {
                String key = this.generateKey(titles);
                rowMap.put(key, new SummaryTable(rowNum, titles));
                rowNum++;
            } else if (CollectionUtil.isNotEmpty(summaryTree.getChildren())) {
                for (SummaryTree child : summaryTree.getChildren()) {
                    rowNum = this.structureRow(rowNum, child, rowMap, new ArrayList<>(titles));
                }
            }
        }
        return rowNum;
    }

    private void structureCell(List<List<ReportingTableDataDTO>> reportingHeaders,
                               int rowNum,
                               int startIndex,
                               Integer columnSpan,
                               List<String> titles,
                               Map<String, SummaryTable> cellMap,
                               Map<String, TargetInfo> targetMap) {
        if (reportingHeaders.size() <= rowNum) {
            return;
        }
        List<ReportingTableDataDTO> currHeader = reportingHeaders.get(rowNum);
        int startColumn = cellMap.size() + startIndex;
        for (int i = startIndex; i < currHeader.size(); ) {
            ReportingTableDataDTO data = currHeader.get(i);
            if (null != columnSpan && (i < startColumn || i >= startColumn + columnSpan)) {
                i = i + data.getColumnSpan();
                continue;
            }
            if (data.getCellHide() || data.getIsTotalColumn()) {
                i = i + data.getColumnSpan();
                continue;
            }
            int startCell = i;
            int endCell = i + (data.getColumnSpan() == 1 ? 0 : data.getColumnSpan() - 1);
            i = i + data.getColumnSpan();

            List<String> copyTitles = new ArrayList<>(titles);
            if (data instanceof ReportingTableDataTargetDTO) {
                ReportingTargetDTO target = ((ReportingTableDataTargetDTO) data).getTarget();

                targetMap.put(target.getKey(), new TargetInfo(target, startCell, endCell));
                copyTitles.add(target.getKey());
                copyTitles.add(target.getTitle());
            } else {
                copyTitles.add(data.getValue());
            }
            // 这个时候已经是最后1级了
            if (data.getColumnSpan() == 1) {
                String key = this.generateKey(copyTitles);
                cellMap.put(key, new SummaryTable(cellMap.size() + startIndex, copyTitles));
            } else {
                structureCell(reportingHeaders, rowNum + 1, startIndex, data.getColumnSpan(), copyTitles, cellMap, targetMap);
            }
        }
    }

    private void filling(ReportingTableDataDTO[][] collectValues,
                         ReportingTargetDTO[] columnTarget,
                         SummaryTree summaryTree,
                         Map<String, SummaryTable> rowMap,
                         Map<String, SummaryTable> cellMap,
                         Map<String, TargetInfo> targetMap,
                         List<ReportingTableDataDTO> rowValues,
                         List<String> cellTitles,
                         List<ReportingTableDataDTO> values) {
        switch (summaryTree.getType()) {
            case ROOT:
                summaryTree.getChildren().forEach(tree -> this.filling(
                        collectValues, columnTarget, tree, rowMap, cellMap, targetMap, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()));
                break;
            case ROW:
                int rowSpan = this.getAllChildNum(summaryTree, SummaryType.ROW);
                List<ReportingTableDataDTO> copyRowValues = rowValues.stream().map(ReportingTableDataDTO::copy).collect(Collectors.toList());
                copyRowValues.add(new ReportingTableDataDTO(summaryTree.getSummary().getValue()).setRowSpan(rowSpan));
                // 子级不是行
                if (SummaryType.ROW != summaryTree.getChildType()) {
                    String rowKey = this.generateKey(copyRowValues.stream().map(ReportingTableDataDTO::getValue).collect(Collectors.toList()));
                    if (rowMap.containsKey(rowKey)) {
                        // 填充行维度的 值、行高
                        for (int i = 0, l = copyRowValues.size(); i < l; i++) {
                            collectValues[rowMap.get(rowKey).index][i].copyProp(copyRowValues.get(i));
                        }
                    }
                }
                // 子级还是行，value 需要重置
                else {
                    values.clear();
                }
                // 还原行高
                rowValues.forEach(e -> e.setRowSpan(1).setCellHide(true));

                List<ReportingTableDataDTO> copyValues = ReportingTableDataDTO.copyList(values);
                summaryTree.getChildren().forEach(tree -> this.filling(
                        collectValues, columnTarget, tree, rowMap, cellMap, targetMap, copyRowValues, new ArrayList<>(), copyValues));
                break;
            case CELL:
                List<String> copyCellTitles = new ArrayList<>(cellTitles);
                copyCellTitles.add(summaryTree.getSummary().getValue());
                summaryTree.getChildren().forEach(tree -> this.filling(
                        collectValues, columnTarget, tree, rowMap, cellMap, targetMap, rowValues, copyCellTitles, values));
                break;
            case TARGET:
                if (targetMap.containsKey(summaryTree.getSummary().getTitle())) {
                    TargetInfo targetInfo = targetMap.get(summaryTree.getSummary().getTitle());
                    copyCellTitles = new ArrayList<>(cellTitles);
                    copyCellTitles.add(0, targetInfo.target.getTitle());
                    copyCellTitles.add(0, targetInfo.target.getKey());

                    String rowKey = this.generateKey(rowValues.stream().map(ReportingTableDataDTO::getValue).collect(Collectors.toList()));
                    Integer rowNum = Optional.ofNullable(rowMap.get(rowKey)).map(e -> e.index).orElse(0);

                    String cellKey = this.generateKey(copyCellTitles);
                    if (cellMap.containsKey(cellKey)) {
                        Integer cellNum = cellMap.get(cellKey).index;
                        ReportingTableDataDTO data = collectValues[rowNum][cellNum];
                        // 填充值
                        data.setValue(targetInfo.target.format(summaryTree.getSummary().getValue()));
                        // 记录行值
                        values.add(data);

                        // 计算小计列 列汇总
                        columnTarget[cellNum] = targetInfo.target;

                        // 计算小计
                        if (targetInfo.startCell <= cellNum && cellNum <= targetInfo.endCell && cellMap.size() > targetMap.size()) {
                            SummaryTypeEnum totalType = SummaryTypeEnum.isExistById(targetInfo.target.getTotalType());
                            if (SummaryTypeEnum.NO != totalType) {
                                ReportingTableDataDTO totalData = collectValues[rowNum][targetInfo.endCell];
                                copyValues = ReportingTableDataDTO.copyList(values).stream()
                                        .peek(v -> v.setValue(targetInfo.target.restore(v.getValue())))
                                        .collect(Collectors.toList());
                                totalType.getSummary().apply(copyValues).ifPresent(total -> totalData.setValue(targetInfo.target.format(total)));

                                // 计算小计列 列汇总
                                columnTarget[targetInfo.endCell] = targetInfo.target;
                            }
                        }
                        // 清空小计记录
                        else {
                            values.clear();
                        }

                    }
                }
                break;
            default:
                throw new BusinessException(RCodeEnum.ERROR_RUNTIME);
        }
    }

    private Integer getAllChildNum(SummaryTree summaryTree, SummaryType type) {
        if (type != summaryTree.getType()) return 0;
        if (type != summaryTree.getChildType()) return 1;
        Integer num = 0;
        for (SummaryTree child : summaryTree.getChildren()) {
            num += this.getAllChildNum(child, type);
        }
        return num;
    }

    private List<ReportingTableDataDTO> calColumnSummary(ReportingTableDataDTO[][] collectValues, ReportingTargetDTO[] columnTarget) {
        List<ReportingTableDataDTO> calColumnSummary = new ArrayList<>();
        for (int i = 0, l = columnTarget.length; i < l; i++) {
            String value = null;
            if (i == 0) {
                // 首列
                value = "列汇总";
            } else {
                ReportingTargetDTO target = columnTarget[i];
                if (Objects.nonNull(target)) {
                    List<ReportingTableDataDTO> curColumn = new ArrayList<>();
                    for (ReportingTableDataDTO[] row : collectValues) {
                        // 指定行指定列，格子有值，则需要列汇总
                        if (Objects.nonNull(row[i].getValue())) {
                            // 复制格子数据，以免污染
                            ReportingTableDataDTO copy = row[i].copy();
                            // 还原格子格式化前数据
                            copy.setValue(target.restore(copy.getValue()));
                            curColumn.add(copy);
                        }
                    }
                    SummaryTypeEnum summaryTypeEnum = SummaryTypeEnum.isExistById(target.getColumnSummaryType());
                    value = summaryTypeEnum.getSummary().apply(curColumn).map(target::format).orElse(null);
                }
            }
            calColumnSummary.add(new ReportingTableDataDTO(value));
        }
        return calColumnSummary;
    }

    private String generateKey(List<String> titles) {
        return MD5.create().digestHex(String.join("##titles##", titles));
    }

    @AllArgsConstructor
    private static class SummaryTable {
        private Integer index;
        private List<String> summaries;
    }

    @AllArgsConstructor
    private static class TargetInfo {
        private ReportingTargetDTO target;
        private Integer startCell;
        private Integer endCell;
    }
}
